iT邦幫忙

2024 iThome 鐵人賽

DAY 24
0
Mobile Development

從 SwiftUI 到 Apple Vision Pro - SwiftUI 從零開始系列 第 24

從 SwiftUI 到 Apple Vision Pro - SwiftUI 從零開始 Day24

  • 分享至 

  • xImage
  •  

Day24 Vision Pro 空間拖曳

模型除了可以以視窗方式呈現,也可以獨立存在,換句話說就是可以將模型從視窗內拖曳到空間的任何位置。

首先,新增一個子View,將讀取模型的程式碼放在子View之內:

struct ContentView: View {
    var body: some View {
        EarthDetailView()
    }
}

struct EarthDetailView: View {
    var body: some View {
        Model3D(named: "Scene", bundle: realityKitContentBundle)
            .padding(.bottom, 50)
    }
}

加入手勢操作,手勢可以針對垂直方向去滑動3D模型:

struct EarthDetailView: View {
    
    @State private var verticalRotation = CGFloat.zero
    @State private var endVerticalRotation = CGFloat.zero
    
    var body: some View {
        Model3D(named: "Scene", bundle: realityKitContentBundle)
            .rotation3DEffect(
                .degrees(-verticalRotation), axis: .x
            )
            .gesture(
                DragGesture()
                    .onChanged({ value in
                        verticalRotation = value.translation.height + endVerticalRotation
                    })
                    .onEnded({ _ in
                        endVerticalRotation = verticalRotation
                    })
            )
        
    }
}

這裡宣告了兩個變數verticalRotation與endVerticalRotation,用來儲存目前手勢拖曳的位置,verticalRotation表示儲存正在拖曳的位置,endVerticalRotation表示儲存結束拖曳的位置。

產生拖曳的效果在gesture屬性內,加入一個DragGesture物件,有兩個控制函式,透過onChanged會回傳正在拖曳的位置。onEnded會回傳結束拖曳的位置。

最後再透過rotation3DEffect屬性,去控制3D模型轉動的範圍。

而目前這裡只有控制垂直方向,水平方向同理:

struct EarthDetailView: View {
    
    @State private var horizontalRotation = CGFloat.zero
    @State private var verticalRotation = CGFloat.zero
    @State private var endHorizontalRotation = CGFloat.zero
    @State private var endVerticalRotation = CGFloat.zero
    
    var body: some View {
        Model3D(named: "Scene", bundle: realityKitContentBundle)
            .rotation3DEffect(
                .degrees(horizontalRotation), axis: .y
            )
            .rotation3DEffect(
                .degrees(-verticalRotation), axis: .x
            )
            .gesture(
                DragGesture()
                    .onChanged({ value in
                        horizontalRotation = value.translation.width + endHorizontalRotation
                        verticalRotation = value.translation.height + endVerticalRotation
                    })
                    .onEnded({ _ in
                        endHorizontalRotation = horizontalRotation
                        endVerticalRotation = verticalRotation
                    })
            )
        
    }
}

使用NavigationSplitView元件,將按鈕與3D模型的子View放入:

struct ContentView: View {
    var body: some View {
        NavigationSplitView {
            Button(action: {
                
            }, label: {
                Text("Circle")
            })
            
        } detail: {
            EarthDetailView()
                .navigationTitle("Circle")
                .toolbar {
                    Button(action: {
                        
                    }, label: {
                        Text("Circle")
                    })
                }
        }
        
    }
}

顯示如圖:

https://ithelp.ithome.com.tw/upload/images/20240824/20162607cPcL4WD0Mg.png

按下按鈕之後,模型就會出現:

顯示如圖:

https://ithelp.ithome.com.tw/upload/images/20240824/20162607BXYMhf5EjX.png

為了要讓模型可以獨立呈現,在toolbar內加入一個openWindow指令,表示可以開啟另外一個視窗:

EarthDetailView()
    .navigationTitle("Circle")
    .toolbar {
        Button(action: {
            openWindow(id: "creatureWindow", value: "")
                            
        }, label: {
            Text("Circle")
        })
}

在這個openWindow指令帶入一個id為creatureWindow,必須要多宣告另外一個WindowGroup,讓這裡的openWindow可以順利啟動另外一組WindowGroup:

@main
struct DemoVisionApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
        
        WindowGroup(id: "creatureWindow", for: String.self) { $modelName in
            EarthDetailView()
                .padding3D([.back, .top], 250)
        }
        .windowStyle(.volumetric)
        .defaultSize(width: 0.5, height: 0.5, depth: 0.5, in: .meters)
    }
}

多宣告一個WindowGroup,id一樣帶入creatureWindow,並且在內部放入EarthDetailView這個子View。

按下右上角的toolbar按鈕之後,就會另外開啟新的視窗:

顯示如圖:

https://ithelp.ithome.com.tw/upload/images/20240824/201626075PgNpiqSXL.png

而這個3D模型視窗,也可以任意拖曳到空間之內:

顯示如圖:

https://ithelp.ithome.com.tw/upload/images/20240824/20162607brFj7ZvhmD.png

完整程式碼:

import SwiftUI
import RealityKit
import RealityKitContent

struct ContentView: View {
    
    @State var isEarth = false
    @Environment(\.openWindow) private var openWindow
    
    var body: some View {
        
        NavigationSplitView {
            List {
                Button(action: {
                    isEarth.toggle()
                    
                }, label: {
                    Text("Earth")
                })
            }
            
        } detail: {
            if isEarth {
                EarthDetailView()
                    .navigationTitle("Earth")
                    .toolbar {
                        Button(action: {
                            openWindow(id: "creatureWindow", value: "")
                            
                        }, label: {
                            Text("Earth")
                        })
                    }
            }
        }
    }
}

struct EarthDetailView: View {
    
    @State private var horizontalRotation = CGFloat.zero
    @State private var verticalRotation = CGFloat.zero
    @State private var endHorizontalRotation = CGFloat.zero
    @State private var endVerticalRotation = CGFloat.zero
    
    var body: some View {
        Model3D(named: "Earth", bundle: realityKitContentBundle)
            .rotation3DEffect(
                .degrees(horizontalRotation), axis: .y
            )
            .rotation3DEffect(
                .degrees(-verticalRotation), axis: .x
            )
            .gesture(
                DragGesture()
                    .onChanged({ value in
                        horizontalRotation = value.translation.width + endHorizontalRotation
                        verticalRotation = value.translation.height + endVerticalRotation
                    })
                    .onEnded({ _ in
                        endHorizontalRotation = horizontalRotation
                        endVerticalRotation = verticalRotation
                    })
            )
    }
}

從 SwiftUI 到 Apple Vision Pro - SwiftUI 從零開始 Day24 [完]


上一篇
從 SwiftUI 到 Apple Vision Pro - SwiftUI 從零開始 Day23
下一篇
從 SwiftUI 到 Apple Vision Pro - SwiftUI 從零開始 Day25
系列文
從 SwiftUI 到 Apple Vision Pro - SwiftUI 從零開始30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言